﻿using IOP.Models.Data.Attributes;
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Linq.Expressions;

namespace IOP.Models.Data
{
    /// <summary>
    /// 数据验证基类
    /// </summary>
    public class DataValidation : IDataValidation
    {

        /// <summary>
        /// 获取当前实体是否存在错误信息
        /// </summary>
        [IgnoreProperty]
        public bool HasErrors
        {
            get
            {
                return _errors.Count > 0;
            }
        }

        /// <summary>
        /// 获取当前实体的所有错误信息列表
        /// </summary>
        [IgnoreProperty]
        public IEnumerable<string> ErrorsList
        {
            get
            {
                foreach (var item in _errors)
                {
                    foreach (var child in item.Value)
                    {
                        yield return child;
                    }
                }
            }
        }

        /// <summary>
        /// 数据验证上下文
        /// </summary>
        private ValidationContext ValidationContext { get; set; } = null;
        /// <summary>
        /// 错误信息字典
        /// </summary>
        private Dictionary<string, List<string>> _errors = new Dictionary<string, List<string>>();

        /// <summary>
        /// 当属性发生变更时
        /// </summary>
        public event PropertyChangingEventHandler PropertyChanging;
        /// <summary>
        /// 当属性发生变更后
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged;
        /// <summary>
        /// 当数据戳发生变更时
        /// </summary>
        public event Action<DataStamp> DataStampChanged;

        /// <summary>
        /// 错误触发事件
        /// </summary>
        public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;

        /// <summary>
        /// 构造函数
        /// </summary>
        public DataValidation()
        {
            NotifyOnInit();
            ValidationContext = new ValidationContext(this);
            ErrorsChanged += ValidationBase_ErrorsChanged;
            ValidateInstance();
        }

        /// <summary>
        /// 获取指定属性的验证错误信息
        /// </summary>
        /// <param name="propertyName"></param>
        /// <returns></returns>
        public IEnumerable GetErrors(string propertyName)
        {
            if (_errors.ContainsKey(propertyName) && _errors[propertyName].Any())
                yield return _errors[propertyName];
        }
        /// <summary>
        /// 获取指定属性的验证错误信息
        /// </summary>
        /// <param name="expression"></param>
        /// <returns></returns>
        public IEnumerable<string> GetPropertyErrors<TProperty>(Expression<Func<TProperty>> expression)
        {
            var propertyNameExpresssion = expression.Body as MemberExpression;
            if (propertyNameExpresssion != null)
                if (_errors.ContainsKey(propertyNameExpresssion.Member.Name) && _errors[propertyNameExpresssion.Member.Name].Any())
                    return _errors[propertyNameExpresssion.Member.Name];
            return new List<string>();
        }
        /// <summary>
        /// 获取指定属性的验证错误信息
        /// </summary>
        /// <param name="propertyName"></param>
        /// <returns></returns>
        public IEnumerable<string> GetPropertyErrors(string propertyName)
        {
            if (_errors.ContainsKey(propertyName) && _errors[propertyName].Any())
                return _errors[propertyName];
            return new List<string>();
        }

        /// <summary>
        /// 属性变更触发函数
        /// </summary>
        /// <param name="propertyName"></param>
        public virtual void OnPropertyChanged(string propertyName) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        /// <summary>
        /// 属性变更触发函数
        /// </summary>
        /// <typeparam name="TProperty"></typeparam>
        /// <param name="expression"></param>
        public virtual void OnPropertyChanged<TProperty>(Expression<Func<TProperty>> expression)
        {
            var propertyNameExpresssion = expression.Body as MemberExpression;
            if (propertyNameExpresssion != null)
            {
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyNameExpresssion.Member.Name));
            }
        }
        /// <summary>
        /// 属性变更时触发函数
        /// </summary>
        /// <param name="propertyName"></param>
        public virtual void OnPropertyChanging(string propertyName) => PropertyChanging?.Invoke(this, new PropertyChangingEventArgs(propertyName));
        /// <summary>
        /// 当数据戳发生变更时
        /// </summary>
        /// <param name="propertyName"></param>
        /// <param name="oldValue"></param>
        /// <param name="newValue"></param>
        public virtual void OnDataStampChanged(string propertyName, object oldValue, object newValue)
        {
            DataStamp stamp = new DataStamp(propertyName, oldValue, newValue);
            DataStampChanged?.Invoke(stamp);
        }

        /// <summary>
        /// 判定属性是否有错误
        /// </summary>
        /// <param name="propertyName"></param>
        /// <returns></returns>
        public bool PropertyHasError(string propertyName)
        {
            return _errors.ContainsKey(propertyName) && _errors[propertyName].Any();
        }
        /// <summary>
        /// 设置属性错误信息
        /// </summary>
        /// <param name="propertyName"></param>
        /// <param name="propertyErrors"></param>
        public void SetErrors(string propertyName, IEnumerable<string> propertyErrors)
        {
            if (propertyErrors == null || !propertyErrors.Any()) return;
            if (_errors.ContainsKey(propertyName))
                foreach (var item in propertyErrors)
                    _errors[propertyName].Add(item);
            else _errors.Add(propertyName, new List<string>(propertyErrors));
            RaiseErrorsChanged(propertyName);
        }
        /// <summary>
        /// 设置属性错误信息
        /// </summary>
        /// <param name="expression"></param>
        /// <param name="propertyErrors"></param>
        public void SetErrors<TProperty>(Expression<Func<TProperty>> expression, IEnumerable<string> propertyErrors)
        {
            var propertyNameExpresssion = expression.Body as MemberExpression;
            if (propertyNameExpresssion != null)
                SetErrors(propertyNameExpresssion.Member.Name, propertyErrors);
        }
        /// <summary>
        /// 设置属性的错误信息
        /// </summary>
        /// <param name="propertyName"></param>
        /// <param name="errorMassage"></param>
        public void SetError(string propertyName, string errorMassage)
        {
            if (string.IsNullOrEmpty(errorMassage) || string.IsNullOrEmpty(propertyName)) return;
            if (_errors.ContainsKey(propertyName))
            {
                var errors = _errors[propertyName];
                if (!errors.Contains(errorMassage)) errors.Add(errorMassage);
            }
            else _errors.Add(propertyName, new List<string> { errorMassage });
            RaiseErrorsChanged(propertyName);
        }
        /// <summary>
        /// 设置属性错误信息
        /// </summary>
        /// <typeparam name="TProperty"></typeparam>
        /// <param name="expression"></param>
        /// <param name="errorMessage"></param>
        public void SetError<TProperty>(Expression<Func<TProperty>> expression, string errorMessage)
        {
            var propertyNameExpresssion = expression.Body as MemberExpression;
            if (propertyNameExpresssion != null)
                SetError(propertyNameExpresssion.Member.Name, errorMessage);
        }

        /// <summary>
        /// 移除验证属性
        /// </summary>
        /// <param name="propertyName"></param>
        public void RemoveError(string propertyName)
        {
            _errors.Remove(propertyName);
            RaiseErrorsChanged(propertyName);
        }
        /// <summary>
        /// 移除验证属性
        /// </summary>
        /// <typeparam name="TProperty"></typeparam>
        /// <param name="expression"></param>
        public void RemoveError<TProperty>(Expression<Func<TProperty>> expression)
        {
            var propertyNameExpresssion = expression.Body as MemberExpression;
            if (propertyNameExpresssion != null)
                _errors.Remove(propertyNameExpresssion.Member.Name);
            RaiseErrorsChanged(propertyNameExpresssion.Member.Name);
        }

        /// <summary>
        /// 验证当前实例
        /// </summary>
        public virtual void ValidateInstance()
        {
            if (ValidationContext == null) ValidationContext = new ValidationContext(this);
            var results = new List<ValidationResult>();
            var isValid = Validator.TryValidateObject(this, ValidationContext, results, true);
            if (!isValid) HandleValidationResults(results);
        }
        /// <summary>
        /// 特性验证调用方法（类型安全）
        /// 如果属性设置了特性验证，那么必须要在属性的Set方法里调用该方法，
        /// 若不调用，特性验证就会失效
        /// </summary>
        public virtual void ValidateProperty<T, TProperty>(T value, Expression<Func<TProperty>> expression)
        {
            var ProperyNameExpresssion = expression.Body as MemberExpression;
            if (ProperyNameExpresssion != null)
            {
                string name = ProperyNameExpresssion.Member.Name;
                ValidateProperty(value, name);
            }
        }
        /// <summary>
        /// 特性验证调用方法
        /// 如果属性设置了特性验证，那么必须要在属性的Set方法里调用该方法，
        /// 若不调用，特性验证就会失效
        /// </summary>
        public virtual void ValidateProperty(object value, string propertyName)
        {
            if (ValidationContext == null) ValidationContext = new ValidationContext(this);
            ValidationContext.MemberName = propertyName;
            var validationResults = new List<ValidationResult>();
            Validator.TryValidateProperty(value, ValidationContext, validationResults);
            if (validationResults.Count > 0) HandleValidationResults(validationResults);
            else _errors.Remove(propertyName);
            RaiseErrorsChanged(propertyName);
        }

        /// <summary>
        /// 清空所有错误
        /// </summary>
        public void ClearErrors() => _errors.Clear();

        /// <summary>
        /// 初始化时发送验证通知
        /// </summary>
        private void NotifyOnInit()
        {
            var type = GetType();
            foreach (var property in type.GetProperties())
                foreach (var custom in property.GetCustomAttributes(false))
                    if (custom is ValidationAttribute) RaiseErrorsChanged(property.Name);
        }
        /// <summary>
        /// 验证结果处理函数
        /// </summary>
        /// <param name="validationResults"></param>
        private void HandleValidationResults(List<ValidationResult> validationResults)
        {
            var resultsByPropertyName = from results in validationResults
                                        from memberNames in results.MemberNames
                                        group results by memberNames into groups
                                        select groups;

            foreach (var property in resultsByPropertyName)
            {
                if (_errors.ContainsKey(property.Key))
                {
                    var set = _errors[property.Key];
                    foreach(var e in property)
                        if (!set.Contains(e.ErrorMessage)) set.Add(e.ErrorMessage);
                }
                else _errors.Add(property.Key, new List<string>(property.Select(x => x.ErrorMessage)));
                RaiseErrorsChanged(property.Key);
            }
        }

        /// <summary>
        /// 错误发生时
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void ValidationBase_ErrorsChanged(object sender, DataErrorsChangedEventArgs e)
        {
            OnPropertyChanged(() => this.HasErrors);
            OnPropertyChanged(() => this.ErrorsList);
        }

        /// <summary>
        /// 触发错误事件
        /// </summary>
        /// <param name="propertyName"></param>
        private void RaiseErrorsChanged(string propertyName) => ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs(propertyName));

        /// <summary>
        /// 对象回收
        /// </summary>
        public void Dispose()
        {
            ErrorsChanged -= ValidationBase_ErrorsChanged;
        }
    }
}
